﻿using System;
using System.Threading;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Interface.Components;

// ReSharper disable CheckNamespace
namespace MugenInjection.Core.Components
// ReSharper restore CheckNamespace
{
    /// <summary>
    /// Represents the base class for components of the <see cref="IInjector"/>.
    /// </summary>
    public abstract class Component : DisposableObject, IComponent
    {
        #region Fields

        private readonly object _locker = new object();

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="Component"/> class.
        /// </summary>
        protected Component()
        {
        }

        #endregion

        #region Property

        /// <summary>
        /// Gets the <see cref="IInjector"/> that owns the <see cref="IComponent"/>.
        /// </summary>
        public IInjector Injector { get; private set; }

        /// <summary>
        /// Indicates whether the component is unique.
        /// </summary>
        public abstract bool IsSingle { get; }

        /// <summary>
        /// Gets the component type. Need to find a component in the container.
        /// </summary>
        public abstract Type ComponentType { get; }

        /// <summary>
        /// Indicates that the component is loaded.
        /// </summary>
        public bool IsLoaded { get; private set; }

        /// <summary>
        /// Indicates that the component is now loading.
        /// </summary>
        public bool IsLoading { get; private set; }

        /// <summary>
        /// Indicates that the component is now unloading.
        /// </summary>
        public bool IsUnloading { get; private set; }

        #endregion

        #region Methods

        /// <summary>
        /// Loads the current component to specified <see cref="IInjector"/>.
        /// </summary>
        /// <param name="injector">The specified <see cref="IInjector"/></param>
        public void Load(IInjector injector)
        {
            Load(injector, true);
        }

        /// <summary>
        /// Loads the current component to specified <see cref="IInjector"/>.
        /// </summary>
        /// <param name="injector">The specified <see cref="IInjector"/></param>
        /// <param name="addToContainer">Indicates whether the component is automatically added to the <see cref="IComponentContainer"/>.</param>
        public void Load(IInjector injector, bool addToContainer)
        {
            EnsureIsNotDisposed();
            Validate.ArgumentNotNull(injector, "injector");
            try
            {
                Monitor.Enter(_locker);
                if (IsLoading) return;
                IsLoading = true;
                if (Injector != null)
                    throw new NotSupportedException(
                        "The component already has a injector associated with it. If you want to use a component in another IInjector, first call the method Unload.");
                OnLoading(this);
                Injector = injector;
                if (addToContainer && !injector.Components.IsContain(this))
                    injector.Components.Add(this);
                OnLoad();
                OnLoaded(this);
                IsLoaded = true;
            }
            finally
            {
                IsLoading = false;
                Monitor.Exit(_locker);
            }
        }

        /// <summary>
        /// Unloads the current component from <see cref="IInjector"/>.
        /// </summary>
        public void Unload()
        {
            EnsureIsNotDisposed();
            try
            {
                Monitor.Enter(_locker);
                if (IsUnloading) return;
                IsUnloading = true;
                if (Injector == null)
                    throw new NotSupportedException("The component is not yet loaded into the IInjector.");
                OnUnloading(this);
                if (Injector.Components.IsContain(this))
                    Injector.Components.Remove(this);
                OnUnload();
                OnUnloaded(this);
                Injector = null;
                IsLoaded = false;
            }
            finally
            {
                IsUnloading = false;
                Monitor.Exit(_locker);
            }
        }

        private void OnLoading(IComponent obj)
        {
            Action<IComponent> handler = Loading;
            if (handler != null) handler(obj);
        }

        private void OnLoaded(IComponent obj)
        {
            Action<IComponent> handler = Loaded;
            if (handler != null) handler(obj);
        }

        private void OnUnloading(IComponent obj)
        {
            Action<IComponent> handler = Unloading;
            if (handler != null) handler(obj);
        }

        private void OnUnloaded(IComponent obj)
        {
            Action<IComponent> handler = Unloaded;
            if (handler != null) handler(obj);
        }

        #endregion

        #region Events

        /// <summary>
        /// Occured after loading current <see cref="Component"/>.
        /// </summary>
        public event Action<IComponent> Loading;

        /// <summary>
        /// Occured after loaded current <see cref="Component"/>.
        /// </summary>
        public event Action<IComponent> Loaded;

        /// <summary>
        /// Occured after unloading current <see cref="Component"/>.
        /// </summary>
        public event Action<IComponent> Unloading;

        /// <summary>
        /// Occured after unloaded current <see cref="Component"/>.
        /// </summary>
        public event Action<IComponent> Unloaded;

        #endregion

        #region Virtual or abstract method

        /// <summary>
        /// Occurs on load component.
        /// </summary>
        protected virtual void OnLoad()
        {
        }

        /// <summary>
        /// Occurs on unload component.
        /// </summary>
        protected virtual void OnUnload()
        {
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        public abstract IComponent Clone();

        #endregion
    }
}